#include "MCP4728.hpp"
#include <string>

using namespace std;

/**
 * @brief run function of thread
 * @param parameter
 */
void MCP4728::run(void *parameter)
{
    rt_thread_mdelay(500);
    if (!this->init(0x00))
        rt_kprintf("MCP4728 init failed!\r\n");
    rt_thread_mdelay(500);
    if (this->reset() != MCP_Status::OK)
        rt_kprintf("MCP4728 reset failed!\r\n");
    rt_thread_mdelay(500);
    if (this->wakeUp() != MCP_Status::OK)
        rt_kprintf("MCP4728 wake up failed!\r\n");

    vector<string> *cmdVec = nullptr;
    while (1)
    {
        cmdVec = this->cmdMail->get();
        if (cmdVec != nullptr)
        {
            this->cmdProcess(*cmdVec);
            cmdVec = nullptr;
        }

        rt_thread_mdelay(500);
    }
}

MCP_Status MCP4728::writeByte(const rt_uint8_t &byte)
{
    if (!this->sendByte(address, byte))
        return MCP_Status::FAULT;

    return MCP_Status::OK;
}

MCP_Status MCP4728::readByte(rt_uint8_t &byte)
{
    if (!this->recvByte(address, byte))
        return MCP_Status::FAULT;

    return MCP_Status::OK;
}

bool MCP4728::init(const rt_uint8_t &addr)
{
    // check address valid
    if (addr > 0x07)
    {
        rt_kprintf("i2c bus addr invalid.\r\n");
        return false;
    }

    //* pin mode set
    rt_pin_mode(LDAC, PIN_MODE_OUTPUT);
    rt_pin_mode(RDY_BSY, PIN_MODE_INPUT);

    /** If LDAC is permanently tied to “Low”, the content of the input register is transferred to its output register (VOUT) immediately at the last input data byte’s acknowledge pulse.*/
    rt_pin_write(LDAC, PIN_LOW);

    //* set IIC bus
    if (!this->i2cInit("i2c1"))
    {
        rt_kprintf("i2c bus init failed.\r\n");
        return false;
    }

    this->channelInfo.insert({Channel::A, ChannelInfo()});
    this->channelInfo.insert({Channel::B, ChannelInfo()});
    this->channelInfo.insert({Channel::C, ChannelInfo()});
    this->channelInfo.insert({Channel::D, ChannelInfo()});
    this->channelInfo[Channel::A].DAC_chnSelect = 0x00;
    this->channelInfo[Channel::B].DAC_chnSelect = 0x01;
    this->channelInfo[Channel::C].DAC_chnSelect = 0x02;
    this->channelInfo[Channel::D].DAC_chnSelect = 0x03;

    //* set address
    this->address = UNI_ADDR | addr;

    //* set reference voltage
    this->vRef = 0x00; // all default: VDD

    //* set gain
    this->gain = 0x00; // all default: 1x

    //* set power down
    this->powerDown = 0x00; // all default: normal mode

    return true;
}

MCP_Status MCP4728::config()
{
    this->writeFast(0x00, 0x00, 0x00, 0x00);

    return this->reset();
}

/**
 * @brief the device will abort the current conversion and perform the following tasks:
• Internal Reset similar to a Power-on Reset (POR). The contents of the EEPROM are loaded into each DAC input and output registers immediately
• V_OUT will be available immediately regardless of the LDAC pin condition
 * @return MCP_Status: OK or FAULT
 */
MCP_Status MCP4728::reset()
{
    if (this->getPin_RDY_BSY() == MCP_Status::BUSY)
        return MCP_Status::FAULT;

    if (!this->sendByte(0x00, MCP_CMD_RESET))
        return MCP_Status::FAULT;

    return MCP_Status::OK;
}

/**
 * @brief the device will reset the Power-Down bits (PD1, PD0 = 0,0).
 * @return MCP_Status: OK or FAULT
 */
MCP_Status MCP4728::wakeUp()
{
    if (this->getPin_RDY_BSY() == MCP_Status::BUSY)
        return MCP_Status::FAULT;

    if (!this->sendByte(0x00, MCP_CMD_WAKEUP))
        return MCP_Status::FAULT;

    return MCP_Status::OK;
}

/**
 * @brief the device updates all DAC analog outputs (V_OUT) at the same time.
 * @return MCP_Status: OK or FAULT
 */
MCP_Status MCP4728::softUpdate()
{
    if (this->getPin_RDY_BSY() == MCP_Status::BUSY)
        return MCP_Status::FAULT;

    if (!this->sendByte(0x00, MCP_CMD_SOFT_UPDATE))
        return MCP_Status::FAULT;

    return MCP_Status::OK;
}

/**
 * @brief read slave address stored in EEPROM and register
 * @param EEPROMaddr address bit in EEPROM
 * @param inputRegAddr address bit in input register
 * @return read status
 */
MCP_Status MCP4728::readSlaveAddress(rt_uint8_t &EEPROMaddr, rt_uint8_t &inputRegAddr)
{
    // do nothing if the device is busy
    if (this->getPin_RDY_BSY() == MCP_Status::BUSY)
        return MCP_Status::FAULT;

    this->setPin_LDAC(1);

    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)this->i2c_bus->priv;
    I2C_Bit::i2c_delay(ops);

    //* first byte
    I2C_Bit::i2c_start(ops);
    I2C_Bit::i2c_writeb(this->i2c_bus, 0x00);

    //* second byte (copied from i2c_writeb), set LDAC pin to 0 before ACK
    rt_int32_t i;
    rt_uint8_t bit;
    rt_uint8_t data = 0x0C;
    for (i = 7; i >= 0; i--)
    {
        SCL_L(ops);
        bit = (data >> i) & 1;
        SET_SDA(ops, bit);
        I2C_Bit::i2c_delay(ops);
        if (I2C_Bit::SCL_H(ops) < 0)
        {
            LOG_D("i2c_writeb: 0x%02x, "
                  "wait scl pin high timeout at bit %d",
                  data, i);
        }
    }
    SCL_L(ops);
    I2C_Bit::i2c_delay(ops);
    this->setPin_LDAC(0);
    I2C_Bit::i2c_waitack(ops);

    //* third byte after restart
    I2C_Bit::i2c_restart(ops);
    I2C_Bit::i2c_writeb(this->i2c_bus, 0xC1);

    //* read fourth byte (without ack prevent SDA keep low)
    rt_uint8_t addrGet = I2C_Bit::i2c_readb(this->i2c_bus);
    I2C_Bit::i2c_send_ack_or_nack(this->i2c_bus, 0);
    I2C_Bit::i2c_delay(ops);
    I2C_Bit::i2c_stop(ops);

    //* check get data validity (A2 A1 A0 1 A2 A1 A0 0)
    if (((addrGet & (1 << 4)) == 0) || ((addrGet & (1 << 0)) != 0))
        return MCP_Status::FAULT;

    //* split addrGet to EEPROMaddr and inputRegAddr
    EEPROMaddr = addrGet >> 5;
    inputRegAddr = (addrGet >> 1) & 0x07;

    return MCP_Status::OK;
}

MCP_Status MCP4728::readGeneral(bool flag)
{
    vector<rt_uint8_t> data;
    data.resize(24);
    if (!this->recv(this->address, data, 24))
        return MCP_Status::FAULT;

    //* flag: 1->EEPROM, 0->input register
    //* channel A
    this->channelInfo[Channel::A].por = data[0 + flag * 3] >> 6 & 0x01;
    this->channelInfo[Channel::A].vRef = static_cast<V_REF>(data[1 + flag * 3] >> 7 & 0x01);
    this->channelInfo[Channel::A].powerDown = static_cast<POWER_DOWN>(data[1 + flag * 3] >> 5 & 0x03);
    rt_uint16_t value = ((data[1 + flag * 3] & 0x0F) << 8) | data[2 + flag * 3];
    this->channelInfo[Channel::A].value = value;

    //* channel B
    this->channelInfo[Channel::B].por = data[6 + flag * 3] >> 6 & 0x01;
    this->channelInfo[Channel::B].vRef = static_cast<V_REF>(data[7 + flag * 3] >> 7 & 0x01);
    this->channelInfo[Channel::B].powerDown = static_cast<POWER_DOWN>(data[7 + flag * 3] >> 5 & 0x03);
    value = ((data[7 + flag * 3] & 0x0F) << 8) | data[8 + flag * 3];
    this->channelInfo[Channel::B].value = value;

    //* channel C
    this->channelInfo[Channel::C].por = data[12 + flag * 3] >> 6 & 0x01;
    this->channelInfo[Channel::C].vRef = static_cast<V_REF>(data[13 + flag * 3] >> 7 & 0x01);
    this->channelInfo[Channel::C].powerDown = static_cast<POWER_DOWN>(data[13 + flag * 3] >> 5 & 0x03);
    value = ((data[13 + flag * 3] & 0x0F) << 8) | data[14 + flag * 3];
    this->channelInfo[Channel::C].value = value;

    //* channel D
    this->channelInfo[Channel::D].por = data[18 + flag * 3] >> 6 & 0x01;
    this->channelInfo[Channel::D].vRef = static_cast<V_REF>(data[19 + flag * 3] >> 7 & 0x01);
    this->channelInfo[Channel::D].powerDown = static_cast<POWER_DOWN>(data[19 + flag * 3] >> 5 & 0x03);
    value = ((data[19 + flag * 3] & 0x0F) << 8) | data[20 + flag * 3];
    this->channelInfo[Channel::D].value = value;

    return MCP_Status::OK;
}

/**
 * @brief set new slave address
 * @param newAddr new address (0b000 to 0b111)
 * @return set status
 */
MCP_Status MCP4728::setSlaveAddress(rt_uint8_t newAddr)
{
    // check new address validity (0b000 to 0b111)
    if (newAddr > 0x07)
        return MCP_Status::FAULT;

    // do nothing if the device is busy
    if (this->getPin_RDY_BSY() == MCP_Status::BUSY)
        return MCP_Status::FAULT;

    //* read current address first
    rt_uint8_t EEPROMaddr, inputRegAddr;
    if (this->readSlaveAddress(EEPROMaddr, inputRegAddr) != MCP_Status::OK)
        return MCP_Status::FAULT;

    //* get current address (0 1 1 0 0 A2 A1 A0)->(A2 A1 A0)
    rt_uint8_t addrCur = 0x07 & EEPROMaddr;

    rt_thread_mdelay(10);
    this->setPin_LDAC(1);

    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)this->i2c_bus->priv;
    I2C_Bit::i2c_delay(ops);

    //* first byte (1 1 0 0 A2 A1 A0 0)
    I2C_Bit::i2c_start(ops);
    rt_uint8_t data = 0xC0 | (addrCur << 1);
    I2C_Bit::i2c_writeb(this->i2c_bus, data);

    //* second byte (0 1 1 A2 A1 A0 0 1) , set LDAC pin to 0 before ACK
    rt_int32_t i;
    rt_uint8_t bit;
    data = 0x61 | (addrCur << 2);
    for (i = 7; i >= 0; i--)
    {
        SCL_L(ops);
        bit = (data >> i) & 1;
        SET_SDA(ops, bit);
        I2C_Bit::i2c_delay(ops);
        if (I2C_Bit::SCL_H(ops) < 0)
        {
            LOG_D("i2c_writeb: 0x%02x, "
                  "wait scl pin high timeout at bit %d",
                  data, i);
        }
    }
    SCL_L(ops);
    I2C_Bit::i2c_delay(ops);
    this->setPin_LDAC(0);
    I2C_Bit::i2c_waitack(ops);

    //* third byte, new address (0 1 1 A2 A1 A0 1 0)
    data = 0x62 | (newAddr << 2);
    I2C_Bit::i2c_writeb(this->i2c_bus, data);

    //* fourth byte, confirm new address (0 1 1 A2 A1 A0 1 1)
    data = 0x63 | (newAddr << 2);
    bool ret = I2C_Bit::i2c_writeb(this->i2c_bus, data);

    I2C_Bit::i2c_stop(ops);

    this->address = UNI_ADDR | newAddr;

    return ret ? MCP_Status::OK : MCP_Status::FAULT;
}

/**
 * @brief Write Voltage Reference Selection Bit (V_REF) to the DAC Input Registers.
 * @param channel A, B, C, D
 * @param refType VDD (5.0V) or Internal Reference (2.048V)
 * @return MCP_Status: OK or FAULT
 */
MCP_Status MCP4728::setRefVoltage(Channel channel, V_REF refType)
{
    //* get origin voltage reference (0 0 0 0 Vfa Vfb Vfc Vfd)
    rt_uint8_t oriVRef = 0x00;
    oriVRef |= static_cast<rt_uint8_t>(this->channelInfo[Channel::A].vRef) << 3; // Vfa
    oriVRef |= static_cast<rt_uint8_t>(this->channelInfo[Channel::B].vRef) << 2; // Vfb
    oriVRef |= static_cast<rt_uint8_t>(this->channelInfo[Channel::C].vRef) << 1; // Vfc
    oriVRef |= static_cast<rt_uint8_t>(this->channelInfo[Channel::D].vRef) << 0; // Vfd

    //* get channel to set (0 or 1 or 2 or 3)
    rt_uint8_t channelMask = static_cast<rt_uint8_t>(channel);
    channelMask = 1 << channelMask;

    //* comments below all set channel B for example
    rt_uint8_t temp;
    switch (refType)
    {
    case V_REF::VDD:
        temp = 0x0F & (~channelMask); //* (0 0 0 0 1 0 1 1)
        temp &= oriVRef;              //* (0 0 0 0 x 0 x x)
        temp |= MCP_CMD_SET_REF_VOL;  //* (1 0 0 0 x 0 x x)
        break;
    case V_REF::Internal:
        temp = oriVRef | channelMask; //* (0 0 0 0 x 1 x x)
        temp |= MCP_CMD_SET_REF_VOL;  //* (1 0 0 0 x 1 x x)
        break;
    default:
        return MCP_Status::FAULT;
    }

    //* send voltage reference (1 0 0 X Vfa Vfb Vfc Vfd)
    MCP_Status ret = this->writeByte(temp);
    if (ret == MCP_Status::OK)
    {
        //* update local data
        this->channelInfo[channel].vRef = refType;
    }
    return ret;
}

/**
 * @brief Write Gain Selection Bit (GX) to the DAC Input Registers.
 * @param channel A, B, C, D
 * @param gain X1 ro X2
 * @return MCP_Status: OK or FAULT
 */
MCP_Status MCP4728::setGain(Channel channel, GAIN gain)
{
    //* get origin gain reference (0 0 0 0 Ga Gb Gc Gd)
    rt_uint8_t oriGain = 0x00;
    oriGain |= static_cast<rt_uint8_t>(this->channelInfo[Channel::A].gain) << 3; // Ga
    oriGain |= static_cast<rt_uint8_t>(this->channelInfo[Channel::B].gain) << 2; // Gb
    oriGain |= static_cast<rt_uint8_t>(this->channelInfo[Channel::C].gain) << 1; // Gc
    oriGain |= static_cast<rt_uint8_t>(this->channelInfo[Channel::D].gain) << 0; // Gd

    //* get channel to set (0 or 1 or 2 or 3)
    rt_uint8_t channelMask = static_cast<rt_uint8_t>(channel);
    channelMask = 1 << channelMask;

    //* comments below all set channel C for example
    rt_uint8_t temp;
    switch (gain)
    {
    case GAIN::X1:
        temp = 0x0F & (~channelMask); //* (0 0 0 0 1 1 0 1)
        temp &= oriGain;              //* (0 0 0 0 x x 0 x)
        temp |= MCP_CMD_SET_GAIN;     //* (1 1 0 0 x x 0 x)
        break;
    case GAIN::X2:
        temp = oriGain | channelMask; //* (0 0 0 0 x x 1 x)
        temp |= MCP_CMD_SET_GAIN;     //* (1 1 0 0 x x 1 x)
        break;
    default:
        return MCP_Status::FAULT;
    }

    //* send gain selection (1 1 0 X Ga Gb Gc Gd)
    MCP_Status ret = this->writeByte(temp);
    if (ret == MCP_Status::OK)
    {
        //* update local data
        this->channelInfo[channel].gain = gain;
    }
    return ret;
}

/**
 * @brief Write Power-Down Selection Bits (PD1, PD0) to the DAC Input Registers.
 * @param channel
 * @param pd normal, 1kΩ, 100kΩ, 500kΩ resistor to GND
 * @return MCP_Status: OK or FAULT
 */
MCP_Status MCP4728::setPowerDown(Channel channel, POWER_DOWN pd)
{
    //* get origin power down (A1 A0 B1 B0 C1 C0 D1 D0)
    rt_uint8_t oriPD = 0x00;
    oriPD |= static_cast<rt_uint8_t>(this->channelInfo[Channel::A].powerDown) << 6; // A1 A0
    oriPD |= static_cast<rt_uint8_t>(this->channelInfo[Channel::B].powerDown) << 4; // B1 B0
    oriPD |= static_cast<rt_uint8_t>(this->channelInfo[Channel::C].powerDown) << 2; // C1 C0
    oriPD |= static_cast<rt_uint8_t>(this->channelInfo[Channel::D].powerDown) << 0; // D1 D0
    //* convert to rt_uint8_t
    rt_uint8_t pdSet = static_cast<rt_uint8_t>(pd);
    rt_uint8_t channelSet = static_cast<rt_uint8_t>(channel);
    //* set mask and clear bits
    rt_uint8_t mask = ~(0x03 << (channelSet * 2));
    rt_uint8_t clearPD = oriPD & mask;
    //* set new power down
    rt_uint8_t newPD = clearPD | (pdSet << (channelSet * 2));

    //* 1 0 1 X A1 A0 B1 B0 C1 C0 D1 D0 x x x x
    rt_uint16_t temp = 0XA000 | (newPD << 4);
    rt_uint8_t highByte = temp >> 8;
    rt_uint8_t lowByte = temp & 0xFF;

    //* send commend
    if (!this->sendByte(this->address, highByte, 1, 0))
        return MCP_Status::FAULT;
    if (!this->sendByte(this->address, lowByte, 0, 1))
        return MCP_Status::FAULT;

    //* update local data
    this->channelInfo[channel].powerDown = pd;

    return MCP_Status::OK;
}

MCP_Status MCP4728::getPin_RDY_BSY() const
{
    if (rt_pin_read(RDY_BSY) == PIN_LOW)
        return MCP_Status::BUSY;
    else
        return MCP_Status::READY;
}

/**
 * @brief Single Write Command: Write to a Single DAC Input Register and EEPROM.
 * @param channel DAC channel
 * @param value  DAC value (rt_uint16_t, max 4095)
 * @return MCP_Status::FAULT or MCP_Status::OK
 */
MCP_Status MCP4728::writeSingleDAC(Channel channel, const rt_uint16_t &value)
{
    //* get DAC channel select
    rt_uint8_t dacChannel = this->channelInfo[channel].DAC_chnSelect;

    //* check value range
    rt_uint16_t dacValue = value > MAX_DAC_VAL ? MAX_DAC_VAL : value;

    rt_uint8_t data;
    vector<rt_uint8_t> dataVec;
    //* 0 1 0 1 1 DAC1 DAC0 UDAC
    data = MCP_CMD_SINGLE_WRITE | (dacChannel << 1);
    dataVec.push_back(data);

    //* Vf PD1 PD0 G D11 D10 D9 D8
    data = (dacValue >> 8) & 0x0F;                                              //* get D11-D8
    data |= static_cast<rt_uint8_t>(this->channelInfo[channel].gain) << 4;      //* get gain
    data |= static_cast<rt_uint8_t>(this->channelInfo[channel].powerDown) << 5; //* get power down
    data |= static_cast<rt_uint8_t>(this->channelInfo[channel].vRef) << 6;      //* get vRef
    dataVec.push_back(data);

    //* D7 D6 D5 D4 D3 D2 D1 D0
    data = dacValue & 0xFF;
    dataVec.push_back(data);

    if (!this->send(this->address, dataVec))
        return MCP_Status::FAULT;

    //* update local data
    this->channelInfo[channel].value = value;

    return MCP_Status::OK;
}

/**
 * @brief Single Write Command: Write to a Single DAC Input Register and EEPROM.
 * @param channel DAC channel
 * @param value  DAC value (float, V)
 * @return MCP_Status::FAULT or MCP_Status::OK
 */
MCP_Status MCP4728::writeSingleDACFloat(Channel channel, const float &value)
{
    if (value > DAC_VDD)
        return MCP_Status::FAULT;

    rt_uint8_t vRefSet = static_cast<rt_uint8_t>(this->channelInfo[channel].vRef);
    rt_uint8_t gainSet = static_cast<rt_uint8_t>(this->channelInfo[channel].gain);
    rt_uint8_t gainCur = gainSet ? 2 : 1;

    rt_uint16_t dacValue;
    if (!vRefSet) // VDD
        dacValue = static_cast<rt_uint16_t>(value * 4096 / DAC_VDD);
    else // Internal
        dacValue = static_cast<rt_uint16_t>(value * 4096 / gainCur / DAC_VInternal);

    dacValue = dacValue > MAX_DAC_VAL ? MAX_DAC_VAL : dacValue;

    return this->writeSingleDAC(channel, dacValue);
}

/**
 * @brief Write DAC Input Registers Sequentially from Channel A to D.
 * @param valueA DAC value (rt_uint16_t, max 4095)
 * @param valueB DAC value (rt_uint16_t, max 4095)
 * @param valueC DAC value (rt_uint16_t, max 4095)
 * @param valueD DAC value (rt_uint16_t, max 4095)
 * @return MCP_Status::FAULT or MCP_Status::OK
 */
MCP_Status MCP4728::writeFast(const rt_uint16_t &valueA, const rt_uint16_t &valueB, const rt_uint16_t &valueC, const rt_uint16_t &valueD)
{
    rt_uint8_t highByte;
    rt_uint8_t lowByte;

    //* check value range
    rt_uint16_t dacValueA = valueA > MAX_DAC_VAL ? MAX_DAC_VAL : valueA;
    rt_uint16_t dacValueB = valueB > MAX_DAC_VAL ? MAX_DAC_VAL : valueB;
    rt_uint16_t dacValueC = valueC > MAX_DAC_VAL ? MAX_DAC_VAL : valueC;
    rt_uint16_t dacValueD = valueD > MAX_DAC_VAL ? MAX_DAC_VAL : valueD;

    vector<rt_uint8_t> dataVec;
    //* send channel A (0 0 PD1 PD0 D11 D10 D9 D8)
    rt_uint8_t pd = static_cast<rt_uint8_t>(this->channelInfo[Channel::A].powerDown);
    highByte = (dacValueA >> 8) & 0x0F; //* get D11-D8
    highByte |= (pd && 0x03) << 4;      //* get power down A
    dataVec.push_back(highByte);

    //* send channel A (D7 D6 D5 D4 D3 D2 D1 D0)
    lowByte = dacValueA & 0xFF; //* get D7-D0
    dataVec.push_back(lowByte);

    //* send channel B (0 0 PD1 PD0 D11 D10 D9 D8)
    pd = static_cast<rt_uint8_t>(this->channelInfo[Channel::B].powerDown);
    highByte = (dacValueB >> 8) & 0x0F; //* get D11-D8
    highByte |= (pd && 0x03) << 4;      //* get power down B
    dataVec.push_back(highByte);

    //* send channel B (D7 D6 D5 D4 D3 D2 D1 D0)
    lowByte = dacValueB & 0xFF; //* get D7-D0
    dataVec.push_back(lowByte);

    //* send channel C (0 0 PD1 PD0 D11 D10 D9 D8)
    pd = static_cast<rt_uint8_t>(this->channelInfo[Channel::C].powerDown);
    highByte = (dacValueC >> 8) & 0x0F; //* get D11-D8
    highByte |= (pd && 0x03) << 4;      //* get power down C
    dataVec.push_back(highByte);

    //* send channel C (D7 D6 D5 D4 D3 D2 D1 D0)
    lowByte = dacValueC & 0xFF; //* get D7-D0
    dataVec.push_back(lowByte);

    //* send channel D (0 0 PD1 PD0 D11 D10 D9 D8)
    pd = static_cast<rt_uint8_t>(this->channelInfo[Channel::D].powerDown);
    highByte = (dacValueD >> 8) & 0x0F; //* get D11-D8
    highByte |= (pd && 0x03) << 4;      //* get power down D
    dataVec.push_back(highByte);

    //* send channel D (D7 D6 D5 D4 D3 D2 D1 D0)
    lowByte = dacValueD & 0xFF; //* get D7-D0
    dataVec.push_back(lowByte);

    if (!this->send(this->address, dataVec))
        return MCP_Status::FAULT;

    //* update local value
    this->channelInfo[Channel::A].value = valueA;
    this->channelInfo[Channel::B].value = valueB;
    this->channelInfo[Channel::C].value = valueC;
    this->channelInfo[Channel::D].value = valueD;

    return MCP_Status::OK;
}

/**
 * @brief Write DAC Input Registers Sequentially from Channel A to D.
 * @param valueA DAC value (float, V)
 * @param valueB DAC value (float, V)
 * @param valueC DAC value (float, V)
 * @param valueD DAC value (float, V)
 * @return MCP_Status::FAULT or MCP_Status::OK
 */
MCP_Status MCP4728::writeFastFloat(const float &valueA, const float &valueB, const float &valueC, const float &valueD)
{
    if (valueA > DAC_VDD || valueB > DAC_VDD || valueC > DAC_VDD || valueD > DAC_VDD)
        return MCP_Status::FAULT;

    //* channel A
    rt_uint8_t vRefSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::A].vRef);
    rt_uint8_t gainSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::A].gain);
    rt_uint8_t gainCur = gainSet ? 2 : 1;

    rt_uint16_t dacValueA;
    if (!vRefSet) // VDD
        dacValueA = static_cast<rt_uint16_t>(valueA * 4096 / DAC_VDD);
    else // Internal
        dacValueA = static_cast<rt_uint16_t>(valueA * 4096 / gainCur / DAC_VInternal);
    dacValueA = dacValueA > MAX_DAC_VAL ? MAX_DAC_VAL : dacValueA;

    //* channel B
    vRefSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::B].vRef);
    gainSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::B].gain);
    gainCur = gainSet ? 2 : 1;

    rt_uint16_t dacValueB;
    if (!vRefSet) // VDD
        dacValueB = static_cast<rt_uint16_t>(valueB * 4096 / DAC_VDD);
    else // Internal
        dacValueB = static_cast<rt_uint16_t>(valueB * 4096 / gainCur / DAC_VInternal);
    dacValueB = dacValueB > MAX_DAC_VAL ? MAX_DAC_VAL : dacValueB;

    //* channel C
    vRefSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::C].vRef);
    gainSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::C].gain);
    gainCur = gainSet ? 2 : 1;

    rt_uint16_t dacValueC;
    if (!vRefSet) // VDD
        dacValueC = static_cast<rt_uint16_t>(valueC * 4096 / DAC_VDD);
    else // Internal
        dacValueC = static_cast<rt_uint16_t>(valueC * 4096 / gainCur / DAC_VInternal);
    dacValueC = dacValueC > MAX_DAC_VAL ? MAX_DAC_VAL : dacValueC;

    //* channel D
    vRefSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::D].vRef);
    gainSet = static_cast<rt_uint8_t>(this->channelInfo[Channel::D].gain);
    gainCur = gainSet ? 2 : 1;

    rt_uint16_t dacValueD;
    if (!vRefSet) // VDD
        dacValueD = static_cast<rt_uint16_t>(valueD * 4096 / DAC_VDD);
    else // Internal
        dacValueD = static_cast<rt_uint16_t>(valueD * 4096 / gainCur / DAC_VInternal);
    dacValueD = dacValueD > MAX_DAC_VAL ? MAX_DAC_VAL : dacValueD;

    return this->writeFast(dacValueA, dacValueB, dacValueC, dacValueD);
}

/**
 * @brief process the cmd from mailbox and execute the corresponding function
 * @param cmdVec cmd vector
 */
void MCP4728::cmdProcess(const vector<string> &cmdVec)
{
    string cmd = cmdVec[0];
    if (cmd == "reset")
    {
        if (this->reset() == MCP_Status::OK)
            rt_kprintf("MCP4728 reset success!\r\n");
        else
            rt_kprintf("MCP4728 reset failed!\r\n");
    }
    else if (cmd == "init")
    {
        if (this->init(0x00))
            rt_kprintf("MCP4728 init success!\r\n");
        else
            rt_kprintf("MCP4728 init failed!\r\n");
    }
    else if (cmd == "wakeup")
    {
        if (this->wakeUp() == MCP_Status::OK)
            rt_kprintf("MCP4728 wakeup success!\r\n");
        else
            rt_kprintf("MCP4728 wakeup failed!\r\n");
    }
    else if (cmd == "soft_update")
    {
        if (this->softUpdate() == MCP_Status::OK)
            rt_kprintf("MCP4728 softUpdate success!\r\n");
        else
            rt_kprintf("MCP4728 softUpdate failed!\r\n");
    }
    else if (cmd == "read_address")
    {
        rt_uint8_t EEPROMaddr;
        rt_uint8_t inputRegAddr;
        if (this->readSlaveAddress(EEPROMaddr, inputRegAddr) == MCP_Status::OK)
        {
            rt_kprintf("MCP4728 Address in EEPROM: [%d], address in Input Register: [%d]!\r\n", EEPROMaddr, inputRegAddr);
        }
        else
            rt_kprintf("MCP4728 readAddr failed!\n");
    }
    else if (cmd == "read")
    {
        if (this->readGeneral(0) == MCP_Status::OK)
        {
            rt_kprintf("MCP4728 read general success!\r\n");
        }
        else
            rt_kprintf("MCP4728 read general failed!\n");
    }
    else if (cmd == "set_address")
    {
        if (cmdVec.size() < 2)
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }
        string addrStr = cmdVec[1];
        rt_uint16_t addr = stoi(addrStr);
        if (this->setSlaveAddress(addr) == MCP_Status::OK)
            rt_kprintf("MCP4728 setAddr: [%d]!\r\n", addr);
        else
            rt_kprintf("MCP4728 setAddr failed!\r\n");
    }
    else if (cmd == "set_ref")
    {
        if (cmdVec.size() < 3)
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process channel
        string channelStr = cmdVec[1];
        Channel channel;
        if (channelStr == "A")
            channel = Channel::A;
        else if (channelStr == "B")
            channel = Channel::B;
        else if (channelStr == "C")
            channel = Channel::C;
        else if (channelStr == "D")
            channel = Channel::D;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process vRef
        string refVolStr = cmdVec[2];
        V_REF vRef;
        if (refVolStr == "VDD")
            vRef = V_REF::VDD;
        else if (refVolStr == "VRef")
            vRef = V_REF::Internal;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        if (this->setRefVoltage(channel, vRef) == MCP_Status::OK)
            rt_kprintf("MCP4728 setVRef success\r\n");
        else
            rt_kprintf("MCP4728 setVRef failed!\r\n");
    }
    else if (cmd == "set_gain")
    {
        if (cmdVec.size() < 3)
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process channel
        string channelStr = cmdVec[1];
        Channel channel;
        if (channelStr == "A")
            channel = Channel::A;
        else if (channelStr == "B")
            channel = Channel::B;
        else if (channelStr == "C")
            channel = Channel::C;
        else if (channelStr == "D")
            channel = Channel::D;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process gain
        string gainStr = cmdVec[2];
        GAIN gain;
        if (gainStr == "1")
            gain = GAIN::X1;
        else if (gainStr == "2")
            gain = GAIN::X2;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        if (this->setGain(channel, gain) == MCP_Status::OK)
            rt_kprintf("MCP4728 setGain success\r\n");
        else
            rt_kprintf("MCP4728 setGain failed!\r\n");
    }
    else if (cmd == "set_pd")
    {
        if (cmdVec.size() < 3)
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process channel
        string channelStr = cmdVec[1];
        Channel channel;
        if (channelStr == "A")
            channel = Channel::A;
        else if (channelStr == "B")
            channel = Channel::B;
        else if (channelStr == "C")
            channel = Channel::C;
        else if (channelStr == "D")
            channel = Channel::D;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process power down
        string pdStr = cmdVec[2];
        POWER_DOWN pd;
        if (pdStr == "0")
            pd = POWER_DOWN::Normal;
        else if (pdStr == "1")
            pd = POWER_DOWN::Resister_1k;
        else if (pdStr == "100")
            pd = POWER_DOWN::Resister_100k;
        else if (pdStr == "500")
            pd = POWER_DOWN::Resister_500k;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        if (this->setPowerDown(channel, pd) == MCP_Status::OK)
            rt_kprintf("MCP4728 setPowerDown success\r\n");
        else
            rt_kprintf("MCP4728 setPowerDown failed!\r\n");
    }
    else if (cmd == "wr_dac")
    {
        if (cmdVec.size() < 3)
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process channel
        string channelStr = cmdVec[1];
        Channel channel;
        if (channelStr == "A")
            channel = Channel::A;
        else if (channelStr == "B")
            channel = Channel::B;
        else if (channelStr == "C")
            channel = Channel::C;
        else if (channelStr == "D")
            channel = Channel::D;
        else
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // process vol
        string volStr = cmdVec[2];
        rt_uint16_t vol = stoi(volStr);
        if (this->writeSingleDAC(channel, vol) == MCP_Status::OK)
            rt_kprintf("MCP4728 set channel [%s] vol: [%d] success\r\n", channelStr.c_str(), vol);
        else
            rt_kprintf("MCP4728 write_single_dac failed! channel [%s] vol: [%d]\r\n", channelStr.c_str(), vol);
    }
    else if (cmd == "wr_fast")
    {
        if (cmdVec.size() < 5)
        {
            rt_kprintf("MCP4728 invalid!\r\n");
            return;
        }

        // vol
        string volAStr = cmdVec[1];
        string volBStr = cmdVec[2];
        string volCStr = cmdVec[3];
        string volDStr = cmdVec[4];
        rt_uint16_t volA = stoi(volAStr);
        rt_uint16_t volB = stoi(volBStr);
        rt_uint16_t volC = stoi(volCStr);
        rt_uint16_t volD = stoi(volDStr);

        if (this->writeFast(volA, volB, volC, volD) == MCP_Status::OK)
            rt_kprintf("MCP4728 writeFast [%d]  [%d]  [%d]  [%d].\r\n", volA, volB, volC, volD);
        else
            rt_kprintf("MCP4728 writeFast failed [%d]  [%d]  [%d]  [%d].\r\n", volA, volB, volC, volD);
    }
    else
    {
        rt_kprintf("MCP4728 invalid!\r\n");
        return;
    }
}
